文章同步發表至 Medium
接續前一天的縣市找景點功能,複習一下我們會有的功能說明:
- 身為一個使用者,我可以點擊地圖上的查詢按鈕,選擇縣市之後找到該縣市的景點。
- 身為一個使用者,查詢縣市景點之後,地圖上只會呈現該縣市的景點。
昨天已經建立好 API,今天要做的就是顯示查詢的頁面,以及第二點重新渲染的部分。
這邊會使用到的套件有:Bootstrap 5 和 Sweet Alert 2,都是我在工作上沒有特別要求前端葉面的時候會使用到的工具,可以輕鬆地就完成一個簡單乾淨的頁面。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>旅遊規劃</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.8.0/dist/leaflet.css"
integrity="sha512-hoalWLoI8r4UszCkZ5kL8vayOGVae1oxXe/2A4AO6J9+580uKHDO3JdHb7NzwwzK5xr/Fs0W40kiNHxM9vyTtQ=="
crossorigin=""/>
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.Default.css">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<style>
body{
height: 100vh;
}
#app, .row, #map{
height: 100%;
}
#search-by-county{
z-index: 1001;
top: 1%;
right: 0;
}
</style>
</head>
<body>
<div id="app" class="container-fluid">
<div class="row align-items-center position-relative">
<div id="map"></div>
<div id="search-by-county" class="position-absolute col-auto">
<button type="button" class="btn btn-lg btn-primary">
查詢
</button>
</div>
</div>
</div>
</body>
</html>
我想做到的效果是地圖上會有一個固定的查詢按鈕,但因為 Leaflet 預設地圖的 z-index 是 1000,所以必須把按鈕的數值再調大一點。成品如下:
有了按鈕之後,我們還需要先透過 Vue 來建立一個查詢的流程,包含:
這個部分我選擇直接用 Sweet Alert 的方式製作。首先要先加入點擊按鈕之後會出發的 alert,我是參考官方網站的範例進行修改的:
<head>
<meta charset="UTF-8">
<title>旅遊規劃</title>
<!-- 略 -->
</head>
<body>
<div id="app" class="container-fluid">
<div class="row align-items-center position-relative">
<div id="map"></div>
<div id="search-by-county" class="position-absolute col-auto">
<button type="button" class="btn btn-lg btn-primary"
@click.prevent="searchScenicByCounty">
<!-- ^^^ 新增點擊事件,綁定 function 名稱 -->
查詢
</button>
</div>
</div>
</div>
<script>
const app = Vue.createApp({
data(){},
methods:{
initMap(){
// ...
},
getScenicSpots(){
// ...
},
searchScenicByCounty(){
Swal.fire({
title: '利用縣市搜尋景點',
// 輸入的類型,select = 下拉式選單
input: 'select',
// 選單的內容,物件名稱為 <option> 中的 value
inputOptions: {
A: '臺北市',
B: '臺中市',
C: '基隆市',
D: '臺南市',
E: '高雄市',
F: '新北市',
G: '宜蘭縣',
H: '桃園市',
I: '嘉義市',
J: '新竹縣',
K: '苗栗縣',
M: '南投縣',
N: '彰化縣',
O: '新竹市',
P: '雲林縣',
Q: '嘉義縣',
T: '屏東縣',
U: '花蓮縣',
V: '臺東縣',
W: '金門縣',
X: '澎湖縣',
Z: '連江縣',
},
// 顯示取消的按鈕和文字設定
showCancelButton: true,
cancelButtonText: '取消',
// 顯示確認的按鈕文字設定
confirmButtonText: '查詢'
}).then((result) => {
if (result.isConfirmed) {
// 按下的按鈕是確認(查詢)的按鈕:打 API
axios({
method: 'get',
url: `./api/ScenicSpot/${result.value}`
}).then(res => {
console.log(res.data);
}).catch(err => {
console.log(err);
})
}
})
},
},
mounted(){
this.getScenicSpots();
}
});
app.mount('#app');
</script>
</body>
</html>
重新整理網頁後應該就會看到 console 印出了下列的訊息:
好耶!那我們是不是可以直接套用前幾天的 initMap()
,渲染到地圖上了呢?
前幾天所使用到的地圖初始化功能在這裡:
initMap(data){
// 設定地圖
let map = L.map('map').setView([24.175339, 120.648586], 19);
// 新增圖磚
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
// 建立點位
let layer = data.map(ele => { L.marker([ele.y, ele.x]).addTo(map) });
// 新增點位到圖磚上
map.addLayer(layer);
}
那我們是不是只要直接套用就可以了呢?
const app = Vue.createApp({
data(){},
methods:{
initMap(data){
// ...
},
getScenicSpots(){
// ...
},
searchScenicByCounty(){
Swal.fire({
// ...
}).then((result) => {
if (result.isConfirmed) {
axios({
method: 'get',
url: `./api/ScenicSpot/${result.value}`
}).then(res => {
// 初始化地圖
this.initMap(res.data);
}).catch(err => {
console.log(err);
})
}
})
},
},
mounted(){
this.getScenicSpots();
}
});
app.mount('#app');
如果你很開心的這樣做,然後重新整理頁面,按下查詢之後,你應該會發現的你的 console 出現了一個 error:
Error: Map container is already initialized.
由於我們在 Vue 掛載時就有用 L.map('map')
先初始化過地圖了,所以重複呼叫的時候就會出現這個錯誤。